using Microsoft.AspNetCore.Mvc;
using FarmDemo.Models;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authorization;
using System.Text;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;


namespace FarmDemo.Controllers;

[ApiController]
[Route("[controller]/[action]")]
public class UserInfoController : ControllerBase
{
    private readonly DataSet db;
    private readonly JWTSettings _jwtsettings;
    private readonly ILogger<UserInfoController> _logger;

    public UserInfoController(DataSet dbCtx, ILogger<UserInfoController> logger, IOptions<JWTSettings> jwtsettings)
    {
        this.db = dbCtx;
        _logger = logger;
        _jwtsettings = jwtsettings.Value;
    }
    private bool UserItemExists(long id)
    {
        return db.User.Any(e => e.UserId == id);
    }


    [HttpGet(Name = "GetAllUser")]
    public IEnumerable<User> GetAllUser()
    {
        return db.User.ToList();
    }
    [HttpGet("{id}")]
    public async Task<ActionResult<User>> GetUser(long id)
    {
        var userItem = await db.User.FindAsync(id);

        if (userItem == null)
        {
            return NotFound();
        }
        return userItem;
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> PutUser(long id, User user)
    {
        if (id != user.UserId)
        {
            return BadRequest();
        }

        var userItem = await db.User.FindAsync(id);
        if (userItem == null)
        {
            return NotFound();
        }
        userItem.userName = user.userName;
        userItem.password = user.password;
        userItem.gender = user.gender;
        userItem.age = user.age;
        userItem.email = user.email;

        try
        {
            await db.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!UserItemExists(id))
        {
            return NotFound();
        }
        return NoContent();
    }


    [HttpPost(Name = "PostUser")]
    public async Task<ActionResult<User>> PostUser(User u)
    {
        await db.User.AddAsync(u);
        await db.SaveChangesAsync();
        // var maxUserId = db.User.Max(u => u.UserId);
        return CreatedAtAction(nameof(PostUser), new { UserId = u.UserId }, u);
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteUser(long id)
    {
        var userItem = await db.User.FindAsync(id);
        if (userItem == null)
        {
            return NotFound();
        }

        db.User.Remove(userItem);
        await db.SaveChangesAsync();

        return NoContent();
    }

    private RefreshToken GenerateRefreshToken()
    {
        RefreshToken refreshToken = new RefreshToken();

        var randomNumber = new byte[32];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(randomNumber);
            refreshToken.Token = Convert.ToBase64String(randomNumber);
        }
        refreshToken.ExpiryDate = DateTime.UtcNow.AddMonths(6);

        return refreshToken;
    }

    private string GenerateAccessToken(int userId)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(_jwtsettings.SecretKey);
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new Claim[]
            {
                    new Claim(ClaimTypes.Name, Convert.ToString(userId))
            }),
            Expires = DateTime.UtcNow.AddDays(1),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
            SecurityAlgorithms.HmacSha256Signature)
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }


    [HttpPost("Login")]
    public async Task<ActionResult<UserWithToken>> Login([FromBody] User user)
    {
        user = await db.User.Include(u => u.Role)
                                    .Where(u => u.email == user.email
                                            && u.password == user.password).FirstOrDefaultAsync();

        UserWithToken userWithToken = null;

        if (user != null)
        {
            RefreshToken refreshToken = GenerateRefreshToken();
            user.RefreshTokens.Add(refreshToken);
            await db.SaveChangesAsync();

            userWithToken = new UserWithToken(user);
            userWithToken.RefreshToken = refreshToken.Token;
        }

        if (userWithToken == null)
        {
            return NotFound();
        }

        //sign your token here here..
        userWithToken.AccessToken = GenerateAccessToken(user.UserId);
        return userWithToken;
    }
    [HttpPost("RegisterUser")]
    public async Task<ActionResult<UserWithToken>> RegisterUser([FromBody] User user)
    {
        db.User.Add(user);
        await db.SaveChangesAsync();

        //load role for registered user
        user = await db.User.Include(u => u.Role)
                                    .Where(u => u.UserId == user.UserId).FirstOrDefaultAsync();

        UserWithToken userWithToken = null;

        if (user != null)
        {
            RefreshToken refreshToken = GenerateRefreshToken();
            user.RefreshTokens.Add(refreshToken);
            await db.SaveChangesAsync();

            userWithToken = new UserWithToken(user);
            userWithToken.RefreshToken = refreshToken.Token;
        }

        if (userWithToken == null)
        {
            return NotFound();
        }

        //sign your token here here..
        userWithToken.AccessToken = GenerateAccessToken(user.UserId);
        return userWithToken;
    }
    [HttpPost("RefreshToken")]
    public async Task<ActionResult<UserWithToken>> RefreshToken([FromBody] RefreshRequest refreshRequest)
    {
        User user = await GetUserFromAccessToken(refreshRequest.AccessToken);

        if (user != null && ValidateRefreshToken(user, refreshRequest.RefreshToken))
        {
            UserWithToken userWithToken = new UserWithToken(user);
            userWithToken.AccessToken = GenerateAccessToken(user.UserId);

            return userWithToken;
        }

        return null;
    }
    [HttpPost("GetUserByAccessToken")]
    public async Task<ActionResult<User>> GetUserByAccessToken([FromBody] string accessToken)
    {
        User user = await GetUserFromAccessToken(accessToken);

        if (user != null)
        {
            return user;
        }

        return null;
    }
    private bool ValidateRefreshToken(User user, string refreshToken)
    {

        RefreshToken refreshTokenUser = db.RefreshTokens.Where(rt => rt.Token == refreshToken)
                                            .OrderByDescending(rt => rt.ExpiryDate)
                                            .FirstOrDefault();

        if (refreshTokenUser != null && refreshTokenUser.UserId == user.UserId
            && refreshTokenUser.ExpiryDate > DateTime.UtcNow)
        {
            return true;
        }

        return false;
    }
    private async Task<User> GetUserFromAccessToken(string accessToken)
    {
        try
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_jwtsettings.SecretKey);

            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false
            };

            SecurityToken securityToken;
            var principle = tokenHandler.ValidateToken(accessToken, tokenValidationParameters, out securityToken);

            JwtSecurityToken jwtSecurityToken = securityToken as JwtSecurityToken;

            if (jwtSecurityToken != null && jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
            {
                var userId = principle.FindFirst(ClaimTypes.Name)?.Value;

                return await db.User.Include(u => u.Role)
                                    .Where(u => u.UserId == Convert.ToInt32(userId)).FirstOrDefaultAsync();
            }
        }
        catch (Exception)
        {
            return new User();
        }

        return new User();
    }

    

}


